#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>

using namespace std;

/*
信号的生命周期：产生->保存->处理

信号能够被认识 主体根据信号产生行为
信号可以随时产生 不一定立即被处理 属于异步动作
信号产生到被处理前有一个时间窗口用于保存信号内容
处理信号的动作有 默认动作 自定义动作 忽略动作

发送信号的本质就是OS修改PCB内的信号位图(unsigned int signal) 将对应的信号bit位置为1
PCB属于OS的内核数据结构 只能由OS进行修改
所以无论以任何方式向目标进程发送信号 最终都是通过OS修改目标进程的信号位图 因此OS需要提供发送/处理信号的系统接口
*/

//为信号设置自定义动作
void handler(int signal)//进程接收到signal信号后不一定立即调用handler 因为CPU运行快 所以肉眼看着像立即调用
{
    printf("恢复了一个信号 信号编号为%d\n", signal);
}

//1.键盘输入产生信号
// int main()
// {
//     //为2号信号设置自定义动作 os向进程发送2号信号后 不再执行默认动作 而是执行设置的自定义动作
//     signal(2, handler);
//     //ctrl+c kill -2->os向进程发送2号信号->进程做出相应的处理动作(不一定立即处理)
//     while (true)
//     {
//         cout << "我是一个进程：" << getpid() << endl;
//         sleep(1);
//     }
//     return 0;
// }

//2.系统调用产生信号
/*
kill 当前进程向任意进程发送任意信号
raise 当前进程向自己发送任意信号
abort 当前进程向自己发送指定信号
*/
// int main(int argc, char* argv[])
// {
//     //当前进程向任意进程发送任意信号
//     // if (argc != 3)
//     // {
//     //     cout << "\nUsage: " << argv[0] << " pid signal" << endl; 
//     // }
//     // pid_t id = stoi(argv[1]);
//     // int sig = stoi(argv[2]);
//     // if (kill(id, sig) != 0)
//     // {
//     //     cerr << "kill: " << strerror(errno) << endl;
//     // }

//     //当前进程向自己发送任意信号
//     // int cnt = 1;
//     // while (cnt <= 10)
//     // {
//     //     cout << "我是一个进程：" << getpid() << endl;
//     //     sleep(1);
//     //     if (cnt >= 5)
//     //     {
//     //         if (raise(3) != 0)//if (kill(getpid(), 3) != 0)
//     //             cerr << "raise: " << strerror(errno) << endl;
//     //     }
//     //     cnt++;
//     // }

//     //当前进程向自己发送指定信号
//     // int cnt = 1;
//     // while (cnt <= 10)
//     // {
//     //     cout << "我是一个进程：" << getpid() << endl;
//     //     sleep(1);
//     //     if (cnt >= 5)
//     //         abort();//kill(getpid(), SIGABRT)
//     //     cnt++;
//     // }

//     return 0;
// }

//硬件异常产生信号
// int main()
// {
//     //除0错误导致的CPU状态寄存器异常
//     // signal(8, handler);
//     // int a = 10;
//     // a /= 0;
//     // while (true)
//     // {
//     //     a = 1;
//     // }
//     /*
//     q1:为什么os能够识别到除0错误？
//     a: 因为除0时 会导致计算结果溢出 cpu状态寄存器中的状态位图中的溢出bit位会置为1
//        os识别到了状态寄存器的异常 转换为8号信号发送给目标进程 目标进程再执行默认动作终止进程
//     q2:为什么os会一直向进程发送信号？
//     a: 因为进程在切换时 会不断地保存和恢复上下文环境 每次恢复上下文时 os都会识别到状态寄存器的异常
//        因此会不停地向目标进程发送信号
//     */

//     //野指针导致的内存管理单元异常(与除0错误类似 页表映射失败 os识别到mmu异常)
//     /*
//     q1:为什么os能够识别到野指针错误？
//     a: 因为页表中没有野指针的映射 使用野指针访问数据时 会导致mmu异常 os识别到mmu异常 就会向目标进程发送信号
//     q2:为什么os会一直向进程发送信号？
//     a: 与cpu状态寄存器异常类似
//     */
//     signal(11, handler);
//     while (true)
//     {
//         int* p = nullptr;
//         *p = 100;
//         sleep(1);
//     }
//     return 0;
// }

//软件条件(pipe和alarm属于linux系统软件)
/*
每个进程都可以通过调用alarm设置闹钟 因此os中可能存在许多闹钟
因此需要通过一种内核数据结构来维护 该结构可以根据实际场景设计
比如利用小堆结构一直轮询检测第一个闹钟的剩余时间 如果第一个到时间 则删除该闹钟 继续检测第二个闹钟
*/
// int main()
// {
//     alarm(10);
//     //alarm(0);//取消上一次设置的闹钟 并返回上次闹钟的剩余秒数
//     while (true)
//     {
//         cout << "我是一个进程 " << getpid() << endl;
//         sleep(1);
//     }
//     return 0;
// }

//核心转储
// int main()
// {
//     int a[10] = {0};
//     a[10000] = 10;//只要数据在main的有效栈区内 就不会报错 因为可以映射到有效的物理内存
//     //SIGSEGV 段错误信号的终止类型为core 只要打开了云服务器的核心转储 就可以在终止时生成core文件
//     return 0;
// }

//信号屏蔽与信号位图
void showPending(sigset_t& pending)
{
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))
            cout << "1";
        else
            cout << "0";
    }
    cout << endl;
}

int main()
{
    signal(2, handler);
    //1 屏蔽指定信号
    sigset_t block, oblock, pending;
    //1.1 初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    sigemptyset(&pending);
    //1.2 添加要屏蔽的信号
    sigaddset(&block, 2);
    //1.3 开始屏蔽
    sigprocmask(SIG_BLOCK, &block, &oblock);

    //2 打印pending表
    int cnt = 1;
    while (true)
    {
        //2.1 初始化
        sigemptyset(&pending);
        //2.2 获取当前进程的pending表
        sigpending(&pending);
        //2.3 打印
        showPending(pending);
        sleep(1);
        cnt++;
        //在cnt < 10期间接收到2号信号 pending位图对应的bit位由0置1
        //2.4 取消信号屏蔽
        if (cnt == 10)
        {
            sigprocmask(SIG_SETMASK, &oblock, &block);//oblock为原信号位图
            //取消信号屏蔽后 os会至少立即递达一个信号 所以进程可能会终止 需要设置自定义动作以打印pending表
            //信号递达后 pending位图对应的bit位由1置0
            sigprocmask(SIG_SETMASK, &block, &oblock);//继续屏蔽信号
            cnt = 0;
        }
    }
    return 0;
}